/**
 *  Copyright (C) 2008 Richard Hall
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the 
 *  GNU General Public License as published by the Free Software Foundation; either version 3 of the 
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, 
 *  see <http://www.gnu.org/licenses>.
 *
 *  Additional permission under GNU GPL version 3 section 7
 *
 *  If you modify this Program, or any covered work, by linking or combining it with [name of library] 
 *  (or a modified version of that library), containing parts covered by the terms of 
 *  [name of library's license], the licensors of this Program grant you additional permission to 
 *  convey the resulting work. {Corresponding Source for a non-source form of such a combination shall 
 *  include the source code for the parts of [name of library] used as well as that of the 
 *  covered work.}
 *
 * 
 */
package com.gpsmedia.utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.gpsmedia.dataobject.NmeaData;

/**
 * This class is responsible for taking the data from the gps log file (in Nmea
 * 0183 format) and creating a LinkedHashMap of NmeaData objects to be used
 * elsewhere.
 * 
 * @author Richard Hall
 */
public class NmeaDataCollector {

    private String gpsFile;
    private String lastSentenceHeader;
    private LinkedHashMap dataMap = new LinkedHashMap();
    private double DOPValue;
    private boolean includeAlt;
    private NmeaData linedata;

    /**
	 * Constructor for this class.
	 * 
	 * @param gpsFile
	 *            The gps log file
	 * @param DOPValue
	 *            The DOP (Dilution of Precision) Value
	 * @param includeAlt
	 *            Whether to include altitude data.
	 */
    public NmeaDataCollector(String gpsFile, double DOPValue, boolean includeAlt) {//, String pictureFolder) {
        try {
            this.linedata = null;
            this.gpsFile = gpsFile;
            this.findNmeaSentenceOrder();
            this.DOPValue = DOPValue;
            this.includeAlt = includeAlt;
            this.readdata();
            printData();
        } catch (Exception ex) {
            Logger.getLogger(NmeaDataCollector.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * A method for taking a separated string (separator can be selected) and outputting
     * an array containing the separated strings  
     * 
	 * @param InputString The String to be split up
	 * @param Seperator The Separator char
	 * @return An array containing the Strings
	 * @throws Exception
	 */
    public String[] StringParser(String InputString, char Seperator) throws Exception {
        try {
            char[] mychar;
            char testchar = ',';
            mychar = InputString.toCharArray();
            String[] collectionArray = new String[50];

            int racount = 1;

            for (int i = 0; i < mychar.length; i++) {
                testchar = mychar[i];
                if (testchar == Seperator) {
                    racount++;
                    collectionArray[racount] = null;
                } else {
                    if (collectionArray[racount] == null) {

                        collectionArray[racount] = "" + testchar;
                    } else {
                        collectionArray[racount] = collectionArray[racount] + testchar;
                    }
                }
            }

            String[] returnArray = new String[racount + 1];

            // done to ensure that an array of the correct size is returned
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = collectionArray[i];
            }

            return returnArray;


        } catch (Exception e) {

            throw new Exception("Exception: error parsing line in StringParser " + InputString);
        }

    }

    /**
	 * Finds the next position in the array for the particular nmea sentence
	 * header. Used with the findNmeaSentenceOrder() method which is used
	 * to find the order of the nmea sentences in the data log.
	 * 
	 * @param data The data String array
	 * @param header The nmea data sentence header
	 * @return -1 if full, -2 if header is already included
	 */
    private int findNextArraySlot(String[][] data, String header) {


        for (int i = 0; i < 4; i++) {
            if (data[1][0].compareToIgnoreCase(header) == 0) {

                return -2;
            }
            if (data[i][0].compareToIgnoreCase("") == 0) {
                return i;
            }
        }
        return -1;
    }

    /**
	 * Compares the contents of the array passed in and checks to see if the
	 * values which are supposed to correspond do. If yes it returns the array
	 * as is, if not everything is moved up a slot. Used by the
	 * findNmeaSentenceOrder() method to adjust the array.
	 * 
	 * @param data
	 *            The String data array
	 * @return The adjusted array
	 */
    private String[][] adjustArray(String[][] data) {
        String slot1 = "";
        String slot2 = "";
        boolean slot1Match = false;
        boolean slot2Match = false;

        for (int i = 0; i < data.length; i++) {
            //time comparison RMC, GGA
            if (data[i][1].compareToIgnoreCase("") != 0) {
                if (slot1.compareToIgnoreCase("") != 0) {
                    if (slot1.compareToIgnoreCase(data[i][1]) == 0) {
                        slot1Match = true;
                    }
                } else {
                    slot1 = data[i][1];
                }
            }
            //track mae good comparison RMC, VTG,
            if (data[i][2].compareToIgnoreCase("") != 0) {
                if (slot2.compareToIgnoreCase("") != 0) {
                    if (slot2.compareToIgnoreCase(data[i][2]) == 0) {
                        slot2Match = true;
                    }
                } else {
                    slot2 = data[i][2];
                }
            }
        }
        if (slot1Match && slot2Match) {
            return data;
        } else {
            for (int i = 1; i <= 3; i++) {
                data[i - 1][0] = data[i][0];
                data[i - 1][1] = data[i][1];
                data[i - 1][2] = data[i][2];
                data[i][0] = "";
                data[i][0] = "";
                data[i][0] = "";

            }
            return data;
        }
    }

    /**
	 * Finds the nmea sentence order so that the correct time data is collected
	 * together in the read method. This is important as not all gps devices log
	 * data in the same order.
	 */
    public void findNmeaSentenceOrder() {
        try {
            File infile = new File(gpsFile);
            FileInputStream inputfilestream = new FileInputStream(infile);

            InputStreamReader inputfilereader = new InputStreamReader(inputfilestream);
            BufferedReader brOrderCheck = new BufferedReader(inputfilereader);

            boolean orderFound = false;
            String[][] nmeaOrder = new String[4][3];
            nmeaOrder[0][0] = "";
            nmeaOrder[0][1] = "";
            nmeaOrder[0][2] = "";
            nmeaOrder[1][0] = "";
            nmeaOrder[1][1] = "";
            nmeaOrder[1][2] = "";
            nmeaOrder[2][0] = "";
            nmeaOrder[2][1] = "";
            nmeaOrder[2][2] = "";
            nmeaOrder[3][0] = "";
            nmeaOrder[3][1] = "";
            nmeaOrder[3][2] = "";

            String lineread = "";


            while ((lineread = brOrderCheck.readLine()) != null && orderFound == false) {
                String[] values = StringParser(lineread, ',');

                if (values[1].compareToIgnoreCase("$GPGGA") == 0 && values[2] != null) {
                    int i = findNextArraySlot(nmeaOrder, "$GPGGA");
                    if (i >= 0) {
                        nmeaOrder[i][0] = "$GPGGA";
                        nmeaOrder[i][1] = values[2];
                    }
                } else if (values[1].compareToIgnoreCase("$GPGSA") == 0) {
                    int i = findNextArraySlot(nmeaOrder, "$GPGSA");
                    if (i >= 0) {
                        nmeaOrder[i][0] = "$GPGSA";
                    }
                } else if (values[1].compareToIgnoreCase("$GPRMC") == 0 && values[9] != null && values[2] != null) {
                    int i = findNextArraySlot(nmeaOrder, "$GPRMC");
                    if (i >= 0) {
                        nmeaOrder[i][0] = "$GPRMC";
                        nmeaOrder[i][1] = values[2];
                        nmeaOrder[i][2] = values[9];
                    }
                } else if (values[1].compareToIgnoreCase("$GPVTG") == 0 && values[2] != null) {
                    int i = findNextArraySlot(nmeaOrder, "$GPVTG");
                    if (i >= 0) {
                        nmeaOrder[i][0] = "$GPVTG";
                        nmeaOrder[i][2] = values[2];
                    }
                }

                if (findNextArraySlot(nmeaOrder, "BLANK") == -1) {
                    nmeaOrder = adjustArray(nmeaOrder);
                    if (nmeaOrder[3][0].compareToIgnoreCase("") != 0) {
                        lastSentenceHeader = nmeaOrder[3][0];
                        orderFound = true;

                    }
                }
            }
        } catch (Exception ex) {
            Logger.getLogger(NmeaDataCollector.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
	 * Reads the data from the gps log file.
	 * 
	 * @throws Exception
	 */
    public void readdata() throws Exception {
        try {

            if (DOPValue == 0) {
                DOPValue = 100;
            }

            File infile = new File(gpsFile);
            FileInputStream inputfilestream = null;

            //test stuff
            int counttest = 0;
            int countnotsuccess = 0;
            //
            inputfilestream = new FileInputStream(infile);



            InputStreamReader inputfilereader = new InputStreamReader(inputfilestream);
            BufferedReader br = new BufferedReader(inputfilereader);

            String lineread = "";

            linedata = new NmeaData();

            while ((lineread = br.readLine()) != null) {
                //check to ensure that line contains NMEA Data
                if (lineread.startsWith("$") == true) {
                    String[] values = StringParser(lineread, ',');

                    if (values[1] != null && values[1].compareToIgnoreCase("$GPGGA") == 0 && values[10] != null) {

                        linedata.setAltitude(Double.valueOf(values[10]).doubleValue());
                    } else if (values[1] != null && values[1].compareToIgnoreCase("$GPRMC") == 0 && values[3].compareToIgnoreCase("A") == 0) {



                        if (values[9] == null) {
                            //means there is no direction
                            linedata.setDirection(0);
                        } else {
                            linedata.setDirection(Double.valueOf(values[9]).doubleValue());
                        }
                        if (values[8] != null) {
                            linedata.setKnotspeed(Double.valueOf(values[8]).doubleValue());
                        } else {
                            linedata.setKnotspeed(0);
                        }
                        linedata.setLatdeg(Integer.valueOf(values[4].copyValueOf(values[4].toCharArray(), 0, 2)).intValue());
                        linedata.setLatmins(Double.valueOf(values[4].copyValueOf(values[4].toCharArray(), 2, values[4].length() - 2)).doubleValue());
                        linedata.setLongdeg(Integer.valueOf(values[6].copyValueOf(values[6].toCharArray(), 0, 3)).intValue());
                        linedata.setLongmins(Double.valueOf(values[6].copyValueOf(values[6].toCharArray(), 3, values[6].length() - 3)).doubleValue());
                        linedata.setLongdir(values[7]);

                        if (linedata.getLongdir().compareTo("W") == 0) {
                            linedata.setLongdec(0 - (linedata.getLongdeg() + (linedata.getLongmins() / 60)));
                        } else {
                            linedata.setLongdec(linedata.getLongdeg() + (linedata.getLongmins() / 60));
                        }
                        linedata.setLatdir(values[5]);

                        if (linedata.getLatdir().compareTo("S") == 0) {
                            linedata.setLatdec(0 - (linedata.getLatdeg() + (linedata.getLatmins() / 60)));
                        } else {
                            linedata.setLatdec(linedata.getLatdeg() + (linedata.getLatmins() / 60));
                        }


                        Calendar date = Calendar.getInstance();
                        date.clear();
                        int year = Integer.valueOf(values[10].copyValueOf(values[10].toCharArray(), 4, 2)).intValue();
                        //nmea data currently uses a 2 digit date. This is a hack/fix. Should work as the data
                    //will be after 2000. I'd be very surpised to find any data pre 2000.
                        year = 2000 + year;
                        //-1 is down to java months starting at 0
                        date.set(year,
                                Integer.valueOf(values[10].copyValueOf(values[10].toCharArray(), 2, 2)).intValue() - 1,
                                Integer.valueOf(values[10].copyValueOf(values[10].toCharArray(), 0, 2)).intValue(),
                                Integer.valueOf(values[2].copyValueOf(values[2].toCharArray(), 0, 2)).intValue(),
                                Integer.valueOf(values[2].copyValueOf(values[2].toCharArray(), 2, 2)).intValue(),
                                Integer.valueOf(values[2].copyValueOf(values[2].toCharArray(), 4, 2)).intValue());

                        linedata.setDate(date);
                    } else if (values[1] != null && values[1].compareToIgnoreCase("$GPGSA") == 0 && values[18] != null) {

                        //this section has code to get around a problem in that not all devices seem
                    //to format the sentences the same way...some GPGSA sentences appear to be shorter
                    //on some devices. To get round this the length of the array of values is checked as the values
                    //I need are always the last 3.
                        counttest++;
                        if (Double.valueOf(values[values.length - 2]) > 4.0) {
                            countnotsuccess++;
                        }

                        linedata.setPdop(Double.valueOf(values[values.length - 3]));
                        linedata.setHdop(Double.valueOf(values[values.length - 2]));
                        linedata.setVdop(Double.valueOf(values[values.length - 1].substring(0, values[values.length - 1].indexOf("*"))));
                    } else if (values[1] != null && values[1].compareToIgnoreCase("$GPVTG") == 0 && values[8] != null) {
                        linedata.setKmhspeed(Double.valueOf(values[8]).doubleValue());
                    }

                    if (lastSentenceHeader.compareToIgnoreCase(values[1]) == 0) {
                        if (includeAlt) {
                            if (linedata.getDate() != null && linedata.getPdop() < DOPValue) {
                                dataMap.put(linedata.getDate(), linedata);
                            }
                        } else {
                            if (linedata.getDate() != null && linedata.getHdop() < DOPValue) {
                                dataMap.put(linedata.getDate(), linedata);
                            }
                        }
                        linedata = new NmeaData();
                    }
                }
            }
            inputfilestream.close();
        } catch (IOException ex) {
            Logger.getLogger(NmeaDataCollector.class.getName()).log(Level.SEVERE, null, ex);
        }



    }

    /** Prints the data collected to the terminal. Used for testing. */
    public void printData() {
        Collection col = dataMap.values();
        Iterator it = col.iterator();
        while (it.hasNext()) {
            NmeaData nd = (NmeaData) it.next();
            System.out.print(nd.toString() + "\r\n");
        }

    }

    /**
	 * Returns the LinkedHashMap of NmeaData
	 * 
	 * @return The LinkedHashMap
	 */
    public LinkedHashMap getData() {
        return dataMap;
    }

    /**
	 * Sets the dataMap field with a LinkedHashMap.
	 * 
	 * @param data
	 *            The LinkedHashMap to be added.
	 */
    public void setData(LinkedHashMap data) {
        dataMap = data;
    }
}


